/*
 * Copyright 2022 Apollo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.ctrip.framework.apollo.adminservice.controller;


import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.google.common.base.Splitter;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@RestController
public class ReleaseController {

    private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
            .trimResults();

    private final ReleaseService releaseService;
    private final NamespaceService namespaceService;
    private final MessageSender messageSender;
    private final NamespaceBranchService namespaceBranchService;

    public ReleaseController(
            final ReleaseService releaseService,
            final NamespaceService namespaceService,
            final MessageSender messageSender,
            final NamespaceBranchService namespaceBranchService) {
        this.releaseService = releaseService;
        this.namespaceService = namespaceService;
        this.messageSender = messageSender;
        this.namespaceBranchService = namespaceBranchService;
    }


    @GetMapping("/releases/{releaseId}")
    public ReleaseDTO get(@PathVariable("releaseId") long releaseId) {
        Release release = releaseService.findOne(releaseId);
        if (release == null) {
            throw new NotFoundException("release not found for %s", releaseId);
        }
        return BeanUtils.transform(ReleaseDTO.class, release);
    }

    @GetMapping("/releases")
    public List<ReleaseDTO> findReleaseByIds(@RequestParam("releaseIds") String releaseIds) {
        Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
                .collect(Collectors.toSet());

        List<Release> releases = releaseService.findByReleaseIds(releaseIdSet);

        return BeanUtils.batchTransform(ReleaseDTO.class, releases);
    }

    @GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/all")
    public List<ReleaseDTO> findAllReleases(@PathVariable("appId") String appId,
                                            @PathVariable("clusterName") String clusterName,
                                            @PathVariable("namespaceName") String namespaceName,
                                            Pageable page) {
        List<Release> releases = releaseService.findAllReleases(appId, clusterName, namespaceName, page);
        return BeanUtils.batchTransform(ReleaseDTO.class, releases);
    }


    @GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/active")
    public List<ReleaseDTO> findActiveReleases(@PathVariable("appId") String appId,
                                               @PathVariable("clusterName") String clusterName,
                                               @PathVariable("namespaceName") String namespaceName,
                                               Pageable page) {
        List<Release> releases = releaseService.findActiveReleases(appId, clusterName, namespaceName, page);
        return BeanUtils.batchTransform(ReleaseDTO.class, releases);
    }

    @GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest")
    public ReleaseDTO getLatest(@PathVariable("appId") String appId,
                                @PathVariable("clusterName") String clusterName,
                                @PathVariable("namespaceName") String namespaceName) {
        Release release = releaseService.findLatestActiveRelease(appId, clusterName, namespaceName);
        return BeanUtils.transform(ReleaseDTO.class, release);
    }

    @Transactional
    @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases")
    public ReleaseDTO publish(@PathVariable("appId") String appId,
                              @PathVariable("clusterName") String clusterName,
                              @PathVariable("namespaceName") String namespaceName,
                              @RequestParam("name") String releaseName,
                              @RequestParam(name = "comment", required = false) String releaseComment,
                              @RequestParam("operator") String operator,
                              @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
        Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
        if (namespace == null) {
            throw new NotFoundException("Could not find namespace for %s %s %s", appId, clusterName,
                    namespaceName);
        }
        Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);

        //send release message
        Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
        String messageCluster;
        if (parentNamespace != null) {
            messageCluster = parentNamespace.getClusterName();
        } else {
            messageCluster = clusterName;
        }
        messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                Topics.APOLLO_RELEASE_TOPIC);
        return BeanUtils.transform(ReleaseDTO.class, release);
    }


    /**
     * merge branch items to master and publish master
     *
     * @return published result
     */
    @Transactional
    @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish")
    public ReleaseDTO updateAndPublish(@PathVariable("appId") String appId,
                                       @PathVariable("clusterName") String clusterName,
                                       @PathVariable("namespaceName") String namespaceName,
                                       @RequestParam("releaseName") String releaseName,
                                       @RequestParam("branchName") String branchName,
                                       @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
                                       @RequestParam(name = "releaseComment", required = false) String releaseComment,
                                       @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
                                       @RequestBody ItemChangeSets changeSets) {
        Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
        if (namespace == null) {
            throw new NotFoundException("Could not find namespace for %s %s %s", appId, clusterName,
                    namespaceName);
        }

        Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName, releaseName,
                releaseComment, isEmergencyPublish, changeSets);

        if (deleteBranch) {
            namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
                    NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
        }

        messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                Topics.APOLLO_RELEASE_TOPIC);

        return BeanUtils.transform(ReleaseDTO.class, release);

    }

    @Transactional
    @PutMapping("/releases/{releaseId}/rollback")
    public void rollback(@PathVariable("releaseId") long releaseId,
                         @RequestParam(name = "toReleaseId", defaultValue = "-1") long toReleaseId,
                         @RequestParam("operator") String operator) {

        Release release;
        if (toReleaseId > -1) {
            release = releaseService.rollbackTo(releaseId, toReleaseId, operator);
        } else {
            release = releaseService.rollback(releaseId, operator);
        }

        String appId = release.getAppId();
        String clusterName = release.getClusterName();
        String namespaceName = release.getNamespaceName();
        //send release message
        messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                Topics.APOLLO_RELEASE_TOPIC);
    }

    @Transactional
    @PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/gray-del-releases")
    public ReleaseDTO publish(@PathVariable("appId") String appId,
                              @PathVariable("clusterName") String clusterName,
                              @PathVariable("namespaceName") String namespaceName,
                              @RequestParam("operator") String operator,
                              @RequestParam("releaseName") String releaseName,
                              @RequestParam(name = "comment", required = false) String releaseComment,
                              @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
                              @RequestParam(name = "grayDelKeys") Set<String> grayDelKeys) {
        Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
        if (namespace == null) {
            throw new NotFoundException("Could not find namespace for %s %s %s", appId, clusterName,
                    namespaceName);
        }

        Release release = releaseService.grayDeletionPublish(namespace, releaseName, releaseComment,
                operator, isEmergencyPublish, grayDelKeys);

        //send release message
        Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
        String messageCluster;
        if (parentNamespace != null) {
            messageCluster = parentNamespace.getClusterName();
        } else {
            messageCluster = clusterName;
        }
        messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                Topics.APOLLO_RELEASE_TOPIC);
        return BeanUtils.transform(ReleaseDTO.class, release);
    }

}
